﻿/****************************************************************************************************
	Copyright (C) 2010 RapidWebDev Organization (http://rapidwebdev.org)
	Author: Eunge, Legal Name: Jian Liu, Email: eunge.liu@RapidWebDev.org

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************************************/

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using BaoJianSoft.Common;
using BaoJianSoft.Common.Caching;
using BaoJianSoft.Common.Data;
using BaoJianSoft.Common.Globalization;
using BaoJianSoft.Common.Validation;
using BaoJianSoft.Platform.Initialization;
using BaoJianSoft.Platform.Properties;

namespace BaoJianSoft.Platform.Linq
{
    /// <summary>
    /// The implementation class of IPermissionApi interface
    /// </summary>
    public class PermissionApi : CachableApi, IPermissionApi
    {
        private static object syncObj = new object();
		private IPlatformConfiguration platformConfiguration;
        private IAuthenticationContext authenticationContext;
        private IRoleApi roleApi;
        private IOrganizationApi organizationApi;
		private string permissionFilePath;
		private volatile Dictionary<CultureInfo, Dictionary<string, IEnumerable<PermissionConfig>>> allPermissionConfigs = new Dictionary<CultureInfo, Dictionary<string, IEnumerable<PermissionConfig>>>();

		/// <summary>
		/// Gets all permission configurations existed in system.
		/// </summary>
		private IEnumerable<PermissionConfig> AllPermissionConfigs
		{
			get
			{
				Dictionary<string, IEnumerable<PermissionConfig>> permissionConfigByDomain = null;
				CultureInfo currentCulture = Resources.Culture ?? CultureInfo.CurrentUICulture ?? CultureInfo.CurrentCulture;
				if (!this.allPermissionConfigs.ContainsKey(currentCulture))
				{
					lock (syncObj)
					{
						if (!this.allPermissionConfigs.ContainsKey(currentCulture))
						{
							string permissionFilePath = Kit.ToAbsolutePath(this.permissionFilePath);
							XmlDocument xmldoc = new XmlDocument();
							xmldoc.Load(permissionFilePath);
							string schemaXml = Kit.GetManifestFile(typeof(IPermissionApi), "PermissionConfig.xsd");
							XmlSchema schema = XmlSchema.Read(new StringReader(schemaXml), null);
							Kit.ValidateXml(schema, xmldoc);

							XmlSerializer serializer = new XmlSerializer(typeof(PermissionConfigs));
							using (FileStream fileStream = new FileStream(permissionFilePath, FileMode.Open, FileAccess.Read))
							{
								PermissionConfigs permissionConfigs = serializer.Deserialize(fileStream) as PermissionConfigs;
								IEnumerable<PermissionConfig> permissionConfigToGlobalize = permissionConfigs.Domain.SelectMany(domain => domain.Permission);
								ReplaceTextGlobalizationIdentifiers(permissionConfigToGlobalize);

								permissionConfigByDomain = permissionConfigs.Domain.ToDictionary(d => d.Value, d => d.Permission.AsEnumerable());
								this.allPermissionConfigs[currentCulture] = permissionConfigByDomain;
							}
						}
					}
				}

				if (!authenticationContext.Identity.IsAuthenticated)
					throw new UnauthorizedAccessException(Resources.InvalidAuthentication);

				if (authenticationContext.Organization == null)
					throw new UnauthorizedAccessException(Resources.InvalidOrganizationID);

				OrganizationTypeObject orgType = organizationApi.GetOrganizationType(authenticationContext.Organization.OrganizationTypeId);
				if (permissionConfigByDomain == null)
					permissionConfigByDomain = this.allPermissionConfigs[currentCulture];

				if (!permissionConfigByDomain.ContainsKey(orgType.Domain))
					return new List<PermissionConfig>();

				return permissionConfigByDomain[orgType.Domain];
			}
		}

        /// <summary>
		/// Construct PermissionApi instance
        /// </summary>
        /// <param name="authenticationContext"></param>
        /// <param name="roleApi"></param>
        /// <param name="organizationApi"></param>
        /// <param name="platformConfiguration"></param>
		/// <param name="permissionFilePath"></param>
		public PermissionApi(IAuthenticationContext authenticationContext, IRoleApi roleApi, IOrganizationApi organizationApi, IPlatformConfiguration platformConfiguration, string permissionFilePath)
        {
            this.authenticationContext = authenticationContext;
            this.roleApi = roleApi;
            this.organizationApi = organizationApi;
            this.platformConfiguration = platformConfiguration;
			this.permissionFilePath = permissionFilePath;
        }

        /// <summary>
        /// Set permissions on specified role.
        /// </summary>
        /// <param name="roleId"></param>
        /// <param name="permissions"></param>
        public void SetRolePermissions(Guid roleId, IEnumerable<string> permissions)
        {
            Kit.NotNull(permissions, "permissions");

            try
            {
                using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
                {
                    ctx.Connection.Open();
                    ctx.Transaction = ctx.Connection.BeginTransaction();

                    try
                    {
                        ctx.Permissions.Delete(p => p.RoleId == roleId);
                        foreach (string permission in permissions.Distinct())
                        {
                            ctx.Permissions.InsertOnSubmit(new Permission() { RoleId = roleId, PermissionKey = permission });
                        }

                        ctx.SubmitChanges();
                        ctx.Transaction.Commit();
                        base.RemoveCache(roleId);
                    }
                    catch
                    {
                        ctx.Transaction.Rollback();
                        throw;
                    }
                }
            }
            catch (Exception exp)
            {
                Logger.Instance(this).Error(exp);
                throw;
            }
        }

        /// <summary>
        /// Set permissions on specified user.
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="permissions"></param>
        public void SetUserPermissions(Guid userId, IEnumerable<string> permissions)
        {
            Kit.NotNull(permissions, "permissions");

            try
            {
                using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
                {
                    ctx.Connection.Open();
                    ctx.Transaction = ctx.Connection.BeginTransaction();

                    try
                    {
                        ctx.Permissions.Delete(p => p.UserId == userId);
                        foreach (string permission in permissions.Distinct())
                        {
                            ctx.Permissions.InsertOnSubmit(new Permission() { UserId = userId, PermissionKey = permission });
                        }

                        ctx.SubmitChanges();
                        ctx.Transaction.Commit();
                        base.RemoveCache(userId);
                    }
                    catch
                    {
                        ctx.Transaction.Rollback();
                        throw;
                    }
                }
            }
            catch (Exception exp)
            {
                Logger.Instance(this).Error(exp);
                throw;
            }
        }

        /// <summary>
        /// Get user owned permissions.
        /// </summary>
        /// <param name="userId">specified user</param>
        /// <param name="explicitOnly">true indicates that returns the permissions only directly set on the user</param>
        /// <returns>returns user permissions</returns>
		public IEnumerable<PermissionObject> FindUserPermissions(Guid userId, bool explicitOnly)
        {
			IEnumerable<PermissionObject> permissions = new List<PermissionObject> { PermissionConst.EVERYONE };
            MembershipDataContext ctx = null;

            try
            {
                var userObject = SpringContext.Current.GetObject<IMembershipApi>().Get(userId);
                if (userObject == null)
                    throw new ValidationException(string.Format(Resources.InvalidUserID, userId));

                if (!explicitOnly)
                {
                    // returns all system permissions always if user is system administrator.
                    if (this.roleApi.IsUserInRole(userId, this.platformConfiguration.Role.RoleId))
						return this.AllPermissionConfigs.Select(config => (PermissionObject)config.Value).Distinct().ToList();

                    foreach (RoleObject roleObject in this.roleApi.FindByUserId(userObject.UserId))
                        permissions = permissions.Union(this.FindRolePermissions(roleObject.RoleId));
                }

				IEnumerable<PermissionObject> permissionsExplicit = base.GetCacheObject<IEnumerable<PermissionObject>>(userId);
                if (permissionsExplicit == null)
                {
                    if (ctx == null)
                        ctx = DataContextFactory.Create<MembershipDataContext>();

					permissionsExplicit = ctx.Permissions.Where(p => p.UserId == userId && p.User.ApplicationId == authenticationContext.ApplicationId)
						.Select(x => (PermissionObject)x.PermissionKey).Distinct().ToList();

                    base.AddCache(userId, permissionsExplicit);
                }

                permissions = permissions.Union(permissionsExplicit);
                return permissions.Distinct();
            }
			catch (ValidationException)
            {
                throw;
            }
            catch (Exception exp)
            {
                Logger.Instance(this).Error(exp);
                throw;
            }
        }

        /// <summary>
        /// Get role permissions.
        /// </summary>
        /// <param name="roleId">specified role</param>
        /// <returns>returns role permissions</returns>
		public IEnumerable<PermissionObject> FindRolePermissions(Guid roleId)
        {
			IEnumerable<PermissionObject> permissions = base.GetCacheObject<IEnumerable<PermissionObject>>(roleId);
            if (permissions != null) return permissions;

            try
            {
                using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
                {
                    permissions = ctx.Permissions.Where(p => p.RoleId.Equals(roleId) && p.Role.ApplicationId == authenticationContext.ApplicationId)
						.Select(p => (PermissionObject)p.PermissionKey).ToList();

                    base.AddCache(roleId, permissions);
                    return permissions;
                }
            }
            catch (Exception exp)
            {
                Logger.Instance(this).Error(exp);
                throw;
            }
        }

        /// <summary>
        /// Returns true if specified user owns permission.
        /// </summary>
        /// <param name="userId">Specified user id</param>
        /// <param name="permissionValue">permission value</param>
        /// <returns></returns>
        public bool HasPermission(Guid userId, string permissionValue)
        {
			Kit.NotNull(permissionValue, "permissionValue");

			return this.HasPermissionInternal(userId, permissionValue);
        }

		/// <summary>
		/// Returns true if the current authenticated user has any permissions in specified permission.
		/// </summary>
		/// <param name="userId">user id</param>
		/// <param name="permissionValue">permission value</param>
		/// <returns>Returns true if the current user has permission to access permission key.</returns>
		public bool HasPermission(string permissionValue)
		{
			Kit.NotNull(permissionValue, "permissionValue");
			if (string.IsNullOrEmpty(permissionValue)) return true;
			if (string.Equals(permissionValue, PermissionConst.ANONYMOUS, StringComparison.OrdinalIgnoreCase)) return true;

			if (!this.authenticationContext.Identity.IsAuthenticated) return false;
			return this.HasPermissionInternal(this.authenticationContext.User.UserId, permissionValue);
		}

		/// <summary>
		/// Returns all permission configurations owned by specified user.
		/// </summary>
		/// <param name="operateUserId">operate user id</param>
		/// <returns>Returns all permission configurations owned by specified user.</returns>
		public IEnumerable<PermissionConfig> FindPermissionConfig(Guid operateUserId)
		{
			try
			{
				string sessionKey = "FindPermissionConfig_" + operateUserId.ToString("N");
				if (authenticationContext.Session[sessionKey] == null)
				{
					IEnumerable<PermissionConfig> permissionConfigs = this.AllPermissionConfigs;

					// if the user is system administrator, he should have all permissions
					if (this.roleApi.IsUserInRole(operateUserId, this.platformConfiguration.Role.RoleId))
						return permissionConfigs;

					List<PermissionObject> permissions = this.FindUserPermissions(operateUserId, false).ToList();
					authenticationContext.Session[sessionKey] = this.FilterPermissionConfigByUserPermission(permissionConfigs, permissions);
				}

				return authenticationContext.Session[sessionKey] as IEnumerable<PermissionConfig>;
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

        #region Private Methods

		private bool HasPermissionInternal(Guid userId, string permissionValue)
		{
			try
			{
				List<PermissionObject> distinctPermissionValues = new List<PermissionObject> { permissionValue };

				// returns true always if user is system administrator.
				if (this.roleApi.IsUserInRole(userId, this.platformConfiguration.Role.RoleId))
					return true;

				IEnumerable<PermissionObject> userPermissions = this.FindUserPermissions(userId, false);
				return distinctPermissionValues.Except(userPermissions).Count() < distinctPermissionValues.Count;
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		private static void ReplaceTextGlobalizationIdentifiers(IEnumerable<PermissionConfig> permissionConfigEnumerable)
		{
			if (permissionConfigEnumerable == null || permissionConfigEnumerable.Count() == 0) return;

			foreach (PermissionConfig permissionConfig in permissionConfigEnumerable)
			{
				permissionConfig.Text = GlobalizationKit.ReplaceGlobalizationVariables(permissionConfig.Text);
				ReplaceTextGlobalizationIdentifiers(permissionConfig.Permission);
			}
		}

		/// <summary>
		/// Filter all permission configs not included in specified user permissions.
		/// </summary>
		/// <param name="permissionConfigEnumerable"></param>
		/// <param name="userPermissions"></param>
		/// <returns></returns>
		private IEnumerable<PermissionConfig> FilterPermissionConfigByUserPermission(IEnumerable<PermissionConfig> permissionConfigEnumerable, IEnumerable<PermissionObject> userPermissions)
		{
			List<PermissionConfig> results = new List<PermissionConfig>();
			foreach (PermissionConfig permissionConfig in permissionConfigEnumerable)
			{
				bool isEmptyPermissionValue = Kit.IsEmpty(permissionConfig.Value);
				if (!isEmptyPermissionValue && !ContainsPermissionValue(userPermissions, permissionConfig.Value))
					continue;

				PermissionConfig newPermissionConfig = new PermissionConfig
				{
					Permission = permissionConfig.Permission,
					Text = permissionConfig.Text,
					Value = permissionConfig.Value,
				};

				if (newPermissionConfig.Permission != null && newPermissionConfig.Permission.Length > 0)
				{
					IEnumerable<PermissionConfig> subPermissionConfigs = FilterPermissionConfigByUserPermission(permissionConfig.Permission, userPermissions);
					newPermissionConfig.Permission = subPermissionConfigs.ToArray();
				}

				if (isEmptyPermissionValue && (newPermissionConfig.Permission == null || newPermissionConfig.Permission.Length == 0))
					continue;

				results.Add(newPermissionConfig);
			}

			return results;
		}

		private bool ContainsPermissionValue(IEnumerable<PermissionObject> permissions, string targetPermissionValue)
		{
			foreach (PermissionObject permissionObject in permissions)
				if (permissionObject.Contains(targetPermissionValue)) return true;

			return false;
		}

        #endregion
    }
}
